use std::{sync::{mpsc, Arc, Mutex}, thread};

/// 线程池的自定义实现
pub struct ThreadPool {
    //threads: Vec<thread::JoinHandle<()>>,
    workers: Vec<Worker>,
    sender: Option<mpsc::Sender<Job>>, // 线程池作为指挥官将Job发送到通道中，由空闲的Worker接收并处理
}

impl ThreadPool {
    /// 创建一个线程池
    /// size 是线程池中线程的数量
    /// 
    /// new方法如果接收小于等于0的size将会panic
    pub fn new(size: usize) -> ThreadPool {
        assert!(size > 0);
        // 初始化线程池
        // 任务通过channel进行分发给各个Worker，所以得创建一下 reciever
        let (sender, receiver) = mpsc::channel();
        let safe_receiver = Arc::new(Mutex::new(receiver));
        //let threads = Vec::with_capacity(size);
        let mut workers = Vec::with_capacity(size);
        // 用workers代替真正执行的线程
        for id in 0..size {
            let safe_receiver_copy = safe_receiver.clone();
            workers.push(Worker::new(id, safe_receiver_copy));
        }
        ThreadPool {workers:workers, sender:Some(sender)}
    }

    pub fn execute<F>(self: &Self, f:F) 
    where 
        F: FnOnce() + Send + 'static,
    {
        let new_job = Box::new(f);
        // 将任务 F 发送到通道之中
        // send被Option包裹之后，使用内部变量的时候需要指名是 可变引用、不可变引用，否则内部变量的所有权就没有了。
        // as_ref的作用是将 Option<T> 转换为 Option<&T> 生成对内部值得不可变引用，避免所有权转移。
        self.sender.as_ref().unwrap().send(new_job).unwrap();
    }
}


// 为Threadpool实现销毁时的清理工作
impl Drop for ThreadPool {
    fn drop(&mut self) {
        // 将每一个worker的线程加入到等待完成的阻塞等待之中
        for worker in &mut self.workers {
            println!("Shutting down worker {}", worker.id);
            println!("drop channel sender {}", worker.id );
            // 我们通过释放掉channel的sender端，来让receiver端收到报错，从而退出阻塞等待状态
            // 但是因为释放要用drop操作，会将sender的所有权拿过来，而此处的drop方法是借用模式
            // 所以我们必须给sender包裹上Option，通过take来窃取。
            drop(self.sender.take());
            println!("join thread {}", worker.id);
            // take会把所有权拿走，替换为 None.
            // 如下使用模式匹配，当take的结果为None的时候啥也不干!!
            if let Some(thread) = worker.thread.take() {
                thread.join().unwrap();
            }
        }
    }
}

/// Worker作为线程池的实际工作对象
/// 持有线程、接收任务、在线程中执行任务
struct Worker {
    id: usize,
    thread: Option<thread::JoinHandle<()>>, // 因为将来要拿走这个变量的所有权，但是Drop Trait只是一个可变引用，无法进行所有权的转移，所以此处用Option包裹，将来用take拿走内部的所有权，这样就只剩下None了
}

impl Worker {
    // 工作对象初始化的时候要持有一个channel的接收端
    // 阻塞着接收即可
    fn new(id:usize, receiver: Arc<Mutex<std::sync::mpsc::Receiver<Job>>>) -> Worker {
        // 阻塞的从channel中读取job
        let thread = thread::spawn(move|| loop {
            // lock方法是为了获取Mutex的锁，避免竞争.
            // let job = receiver.lock().unwrap().recv().unwrap();
            // 区分消息是job还是结束
            let message = receiver.lock().unwrap().recv();
            match message {
                Ok(job) => {
                    println!("Worker {id} got a job; executing...");
                    job();
                },
                Err(_) => {
                    println!("Worker {id} disconnected; shutting down...");
                    break; // 出错之后跳出loop循环
                },
            }
        });
        // 变量名和字段名相同的时候可以省略冒号的写法，直接写成 Worker{id, thread} 即可，但是如下就不行了，因为Option类型不可能有变量名和他相同.
        Worker {id:id, thread:Some(thread)}
    }
}

/// Job 作为实际的任务对象
/// 由线程池对象发送到channel中，并由空闲状态的Worker接收然后处理
//struct Job;
// Job 从一个struct改为一个类型别名，因为实际上我们的任务就是一个闭包而已
// 此处我们要求闭包必须实现了 FnOnce() 特征，必须实现了 Send特征， 生命周期必须是 'static
// 如何给上边的复合类型定义一个别名呢？
// 如果直接使用 type Job = FnOnce() + Send + 'static; 会提示 expected a type found a trait
// 也就是说这不是类型，你得用类型来定义别名
// 让Trait当作类型来使用的话，只能是 dyn Trait了
// ---
// Box<T> 是一个智能指针，用于在堆上分配内存。此处包装了一个实现了FnOnce()特征的trait对象，表示可以被调用一次的闭包或者函数。
//       + Send 是一个标记Trait，表示该类型可以安全的在线程之间传递。
// --- 为什么需要这种形式呢
// 之所以使用 Box<> 是为了在编译的时候知道大小，因为Box可以在腿上分配固定的大小和布局。
type Job = Box<dyn FnOnce() + Send + 'static>;